
/**
 * AppleIIGo
 * GCR 5&3 Disk Image class
 * (C) 2008 by Marc S. Ressl (mressl@umich.edu)
 * Released under the GPL
 */

#include <string.h>
#include "GCR53DiskImage.h"
#include "FDIDiskImage.h"

UINT8 gcr53SectorOrder[] = {
	0, 10, 7, 4, 1, 11, 8, 5, 2, 12, 9, 6, 3
};

bool GCR53DiskImage::isDiskImageValid(UINT32 diskType,
									  UINT32 diskImageByteNum) {
	if (diskType != FLOPPYDISKTYPE_5_25)
		return false;
	
	for (UINT32 gcrTrackNum = 35; gcrTrackNum <= 40; gcrTrackNum++)
		if (diskImageByteNum == (gcrTrackNum * GCR53_TRACK_BYTENUM))
			return true;
	
	return false;
}

GCR53DiskImage::GCR53DiskImage(char * diskImagePath,
							   UINT32 diskImageOffset,
							   UINT32 diskImageByteNum,
							   UINT8 gcrVolume,
							   bool isWriteProtectionForced) :
GCRDiskImage(FLOPPYDISKTYPE_5_25, diskImagePath, GCRTYPE_5_3) {
	if (!isDiskImageOpened())
		return;
	
	this->diskImageOffset = diskImageOffset;
	
	this->gcrVolume = gcrVolume;
	diskImageTrackNum = diskImageByteNum / GCR53_TRACK_BYTENUM;
	
	if (!allocateDisk())
		return;
	
	if (!readDiskImage(diskImageOffset, diskImageTrackNum))
		return;
	
	setWriteProtected(isWriteProtected() || isWriteProtectionForced);
}

GCR53DiskImage::~GCR53DiskImage() {
	if (!isDiskImageOpened())
		return;
	
	if (!isModified())
		return;
	
	if (!writeDiskImage(diskImageOffset, false))
		FDIDiskImage::writeDiskImage(this);
	else
		writeDiskImage(diskImageOffset, true);
}

void GCR53DiskImage::getDiskTrack(UINT32 * trackIndex, UINT32 * headIndex, 
								  UINT32 * diskImageTrackByteNum,
								  UINT32 diskImageTrackIndex) {
	*trackIndex = (diskImageTrackIndex < diskImageTrackNum) ?
		diskImageTrackIndex * 4 : 0;
	*headIndex = 0;
	*diskImageTrackByteNum = GCR53_TRACK_BYTENUM;
}

void GCR53DiskImage::getDiskImageTrack(UINT32 * diskImageTrackIndex,
									   UINT32 * diskImageTrackByteNum,
									   UINT32 trackIndex, UINT32 headIndex) {
	*diskImageTrackIndex = trackIndex / 4;
	if (headIndex || (trackIndex & 0x3) ||
		(*diskImageTrackIndex >= diskImageTrackNum))
		*diskImageTrackByteNum = 0;
	else
		*diskImageTrackByteNum = GCR53_TRACK_BYTENUM;
}

void GCR53DiskImage::writeAddressField(UINT32 diskImageTrackIndex,
									   UINT32 sectorIndex) {
	UINT32 gcrTrackIndex = diskImageTrackIndex;
	
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0xb5);
	
	writeFMValue(gcrVolume);
	writeFMValue(gcrTrackIndex);
	writeFMValue(sectorIndex);
	writeFMValue(gcrVolume ^ gcrTrackIndex ^ sectorIndex);
	
	writeNibble(0xde);
	writeNibble(0xaa);
	writeNibble(0xeb);
}

void GCR53DiskImage::writeDataField(UINT8 * sectorData) {
	writeNibble(0xd5);
	writeNibble(0xaa);
	writeNibble(0xad);
	
	initGcrChecksum();
	
	writeGcrValue(sectorData[0xff] & 0x7);
	
	for (SINT32 blockIndex = 2; blockIndex >= 0; blockIndex--)
		for (SINT32 index = 0; index < 0x33; index++)
			writeGcrValue(((sectorData[index * 5 + blockIndex] & 0x7) << 2) |
						  (getBit(sectorData[index * 5 + 3],
								  1 << (2 - blockIndex)) << 1) |
						  (getBit(sectorData[index * 5 + 4],
								  1 << (2 - blockIndex)) << 0));
	
	for (SINT32 blockIndex = 0; blockIndex < 5; blockIndex++)
		for (SINT32 index = 0x32; index >= 0; index--)
			writeGcrValue(sectorData[index * 5 + blockIndex] >> 3);
	
	writeGcrValue(sectorData[0xff] >> 3);
	
	writeGcrChecksum();
	
	writeNibble(0xde);
	writeNibble(0xaa);
	writeNibble(0xeb, 3);
}

void GCR53DiskImage::writeTrack(UINT32 diskImageTrackIndex,
								UINT8 * diskImageTrackData) {
	writeSync(0x80, 9, 8);
	
	for (UINT32 index = 0; index < GCR53_TRACK_SECTORNUM; index++) {
		UINT32 sectorIndex = gcr53SectorOrder[index];
		if (index)
			writeSync(0x28, 9, 8);
		writeAddressField(diskImageTrackIndex, sectorIndex);
		writeSync(0x0e, 9, 9);
		writeDataField(&diskImageTrackData[GCR53_SECTOR_BYTENUM * sectorIndex]);
	}
}

bool GCR53DiskImage::readAddressField(UINT32 diskImageTrackIndex,
									  UINT32 sectorIndex) {
	UINT32 gcrTrackIndex = diskImageTrackIndex;
	
	for (UINT32 index = 0; index < ((DISK_5_25_TRACK_BITNUM * 2) / 8);
		 index++) {
		if (readNibble() != 0xd5)
			continue;
		if (readNibble() != 0xaa)
			continue;
		if (readNibble() != 0xb5)
			continue;
		
		UINT32 readGcrVolume = readFMValue();
		UINT32 readGcrTrackIndex = readFMValue();
		UINT32 readSectorIndex = readFMValue();
		UINT32 checksum = readFMValue();
		if (checksum != (readGcrVolume ^ readGcrTrackIndex ^ readSectorIndex))
			continue;
		if (gcrTrackIndex != readGcrTrackIndex)
			continue;
		if (sectorIndex != readSectorIndex)
			continue;
		
		if (readNibble() != 0xde)
			continue;
		if (readNibble() != 0xaa)
			continue;
		
		return true;
	}
	
	return false;
}

bool GCR53DiskImage::readDataField(UINT8 * sectorData) {
	for (UINT32 index = 0; index < 30; index++) {
		if (readNibble() != 0xd5)
			continue;
		if (readNibble() != 0xaa)
			continue;
		if (readNibble() != 0xad)
			continue;
		
		initGcrChecksum();
		
		sectorData[0xff] = readGcrValue();
		
		for (SINT32 blockIndex = 2; blockIndex >= 0; blockIndex--)
			for (SINT32 index = 0; index < 0x33; index++) {
				UINT8 value = readGcrValue();
				sectorData[index * 5 + blockIndex] = value >> 2;
				setBit(sectorData[index * 5 + 3], 1 << (2 - blockIndex),
					   (value >> 1) & 0x1);
				setBit(sectorData[index * 5 + 4], 1 << (2 - blockIndex),
					   (value >> 0) & 0x1);
			}
		
		for (SINT32 blockIndex = 0; blockIndex < 5; blockIndex++)
			for (SINT32 index = 0x32; index >= 0; index--) {
				sectorData[index * 5 + blockIndex] &= 0x7;
				sectorData[index * 5 + blockIndex] |= readGcrValue() << 3;
			}
		
		sectorData[0xff] |= readGcrValue() << 3;
		
		if (!checkGcrChecksum())
			return false;
		
		if (readNibble() != 0xde)
			return false;
		if (readNibble() != 0xaa)
			return false;
		
		return true;
	}
	
	memset(sectorData, 0, GCR53_SECTOR_BYTENUM);
	
	return true;
}

bool GCR53DiskImage::readTrack(UINT32 diskImageTrackIndex,
							   UINT8 * diskImageTrackData) {
	if (getNibbleNum() < (GCR53_TRACK_BYTENUM * 8 / 5))
		return false;
	
	for (UINT32 index = 0; index < GCR53_TRACK_SECTORNUM; index++) {
		UINT32 sectorIndex = gcr53SectorOrder[index];
		if (!readAddressField(diskImageTrackIndex, sectorIndex))
			return false;
		if (!readDataField(&diskImageTrackData[GCR53_SECTOR_BYTENUM * 
											   sectorIndex]))
			return false;
	}
	
	return true;
}
